package com.liuwa.common.utils;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.liuwa.common.annotation.Dict;
import com.liuwa.common.annotation.DictValue;
import com.liuwa.common.constant.Constants;
import com.liuwa.common.constant.SysConstants;
import com.liuwa.common.core.domain.BaseEntity;
import com.liuwa.common.core.domain.entity.SysDictData;
import com.liuwa.common.core.domain.entity.SysDictType;
import com.liuwa.common.core.domain.model.SysDictDataOption;
import com.liuwa.common.core.redis.RedisCache;
import com.liuwa.common.core.service.CurdService;
import com.liuwa.common.utils.spring.SpringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 字典工具类
 *
 * @author liuwa
 */
public class DictUtils
{
    /**
     * 分隔符
     */
    public static final String SEPARATOR = ",";

    /**
     * 实体字典
     */
    private static List<SysDictType> sysDictTypes;

    /**
     * redis 缓存
     */
    private static RedisCache redisCache = SpringUtils.getBean(RedisCache.class);

    /**
     * 本地高效缓存
     */
    private static Cache<String, List<SysDictDataOption>> DICT_DATA_CACHE = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(Duration.ofMinutes(1))
            .build();

    /**
     * 设置字典缓存
     *
     * @param key 参数键
     */
    public static List<SysDictDataOption> setDictCache(String key)
    {
        return setDictCache(key, new ArrayList<SysDictDataOption>());
    }

    /**
     * 设置字典缓存
     *
     * @param key 参数键
     * @param sysDictDataOptions 字典数据列表
     */
    public static List<SysDictDataOption> setDictCache(String key, List<SysDictDataOption> sysDictDataOptions) {
        redisCache.setCacheObject(getCacheKey(key), sysDictDataOptions);
        return sysDictDataOptions;
    }

    /**
     * 设置字典缓存
     * @param key 参数键
     * @param sysDictDataOptions 字典数据列表
     * @param timeout 过期时长
     * @return
     */
    public static List<SysDictDataOption> setDictCache(String key, List<SysDictDataOption> sysDictDataOptions, int timeout) {
        redisCache.setCacheObject(getCacheKey(key), sysDictDataOptions, timeout);
        return sysDictDataOptions;
    }

    /**
     * 设置字典缓存
     *
     * @param key 参数键
     * @param sysDictDataList 字典数据列表
     * @param dictType 字典类型
     */
    public static List<SysDictDataOption> setDictCache(String key, List<SysDictData> sysDictDataList, SysDictType dictType) {
        List<SysDictDataOption> options = new ArrayList<SysDictDataOption>();
        String dataType = StringUtils.isEmpty(dictType.getDataType()) ? SysDictType.DATA_TYPE_STRING : dictType.getDataType();
        SysDictDataOption option = null;
        for(SysDictData dataItem : sysDictDataList){
            String label = dataItem.getDictLabel();
            String value = dataItem.getDictValue();
            if(SysDictType.DATA_TYPE_NUMBER.equals(dataType)){
                option = new SysDictDataOption<Double>();
                option.setDictValue(new BigDecimal(value));
            }
            else if(SysDictType.DATA_TYPE_BOOLEAN.equals(dataType)){
                option = new SysDictDataOption<Boolean>();
                option.setDictValue(Boolean.valueOf(value));

            }
            else{
                option = new SysDictDataOption<String>();
                option.setDictValue(value);
            }
            option.setDictLabel(label);
            option.setCssClass(dataItem.getCssClass());
            option.setListClass(dataItem.getListClass());
            options.add(option);
        }
        return setDictCache(key, options);
    }

    /**
     * 获取字典缓存
     *
     * @param key 参数键
     * @return dictDatas 字典数据列表
     */
    public static List<SysDictDataOption> getDictCache(String key)
    {
        key = getCacheKey(key);
        List<SysDictDataOption> dictDataOptions = DICT_DATA_CACHE.getIfPresent(key);
        if(dictDataOptions != null){
            return dictDataOptions;
        }
        Object cacheObj = redisCache.getCacheObject(key);
        if (StringUtils.isNotNull(cacheObj))
        {
            List<SysDictDataOption> dictDatas = (List<SysDictDataOption>)cacheObj;
            return dictDatas;
        }
        return new ArrayList<SysDictDataOption>();
    }

    /**
     * 获取字典
     *
     * @param dictType 参数键
     * @return dictDatas 字典数据列表
     */
    public static List<SysDictDataOption> getDictDataOption(String dictType) {
        List<SysDictDataOption> dictDataOptions = getDictCache(dictType);
        if (dictDataOptions.size() > 0) {
            return dictDataOptions;
        }

        // 实体对象字典选项
        if(dictType.startsWith(SysConstants.DICT_SYS_ENTITY)){
            dictDataOptions = getSysEntityDictOptions(dictType);
        }
        return dictDataOptions;
    }



    /**
     * 根据字典类型和字典值获取字典标签
     *
     * @param dictType 字典类型
     * @param dictValue 字典值
     * @return 字典标签
     */
    public static String getDictLabel(String dictType, String dictValue)
    {
        return getDictLabel(dictType, dictValue, SEPARATOR);
    }

    /**
     * 根据字典类型和字典标签获取字典值
     *
     * @param dictType 字典类型
     * @param dictLabel 字典标签
     * @return 字典值
     */
    public static String getDictValue(String dictType, String dictLabel)
    {
        return getDictValue(dictType, dictLabel, SEPARATOR);
    }

    /**
     * 根据字典类型和字典值获取字典标签
     *
     * @param dictType 字典类型
     * @param dictValue 字典值
     * @return 字典标签
     */
    public static String getDictLabel(String dictType, Number dictValue)
    {
        return getDictLabel(dictType, String.valueOf(dictValue));
    }

    /**
     * 根据字典类型和字典值获取字典标签
     *
     * @param dictType 字典类型
     * @param dictValue 字典值
     * @param separator 分隔符
     * @return 字典标签
     */
    public static String getDictLabel(String dictType, String dictValue, String separator)
    {
        StringBuilder propertyString = new StringBuilder();
        List<SysDictDataOption> dataList = getDictDataOption(dictType);

        if (StringUtils.containsAny(separator, dictValue) && StringUtils.isNotEmpty(dataList))
        {
            for (SysDictDataOption dict : dataList)
            {
                for (String value : dictValue.split(separator))
                {
                    if (value.equals(String.valueOf(dict.getDictValue())))
                    {
                        propertyString.append(dict.getDictLabel() + separator);
                        break;
                    }
                }
            }
        }
        else
        {
            for (SysDictDataOption dict : dataList)
            {
                if (dictValue.equals(String.valueOf(dict.getDictValue())))
                {
                    return dict.getDictLabel();
                }
            }
        }
        return StringUtils.stripEnd(propertyString.toString(), separator);
    }

    /**
     * 根据字典类型和字典标签获取字典值
     *
     * @param dictType 字典类型
     * @param dictLabel 字典标签
     * @param separator 分隔符
     * @return 字典值
     */
    public static String getDictValue(String dictType, String dictLabel, String separator)
    {
        if(StringUtils.isEmpty(dictLabel)){
            return "";
        }
        StringBuilder propertyString = new StringBuilder();
        List<SysDictDataOption> datas = getDictDataOption(dictType);

        if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas))
        {
            for (SysDictDataOption dict : datas)
            {
                for (String label : dictLabel.split(separator))
                {
                    if (label.equals(dict.getDictLabel()))
                    {
                        propertyString.append(dict.getDictValue() + separator);
                        break;
                    }
                }
            }
        }
        else
        {
            for (SysDictDataOption dict : datas)
            {
                if (dictLabel.equals(dict.getDictLabel()))
                {
                    return String.valueOf(dict.getDictValue());
                }
            }
        }
        return StringUtils.stripEnd(propertyString.toString(), separator);
    }

    /**
     * 删除指定字典缓存
     *
     * @param key 字典键
     */
    public static void removeDictCache(String key)
    {
        redisCache.deleteObject(getCacheKey(key));
    }

    /**
     * 清空字典缓存
     */
    public static void clearDictCache()
    {
        Collection<String> keys = redisCache.keys(Constants.SYS_DICT_KEY + "*");
        redisCache.deleteObject(keys);
    }

    /**
     * 设置cache key
     *
     * @param configKey 参数键
     * @return 缓存键key
     */
    public static String getCacheKey(String configKey)
    {
        return Constants.SYS_DICT_KEY + configKey;
    }

    /**
     * 获取实体字典
     * @return
     */
    public synchronized static List<SysDictType> getSysDictTypes(){
        if(sysDictTypes != null){
            return sysDictTypes;
        }
        sysDictTypes = new ArrayList<SysDictType>();
        List<Class> classList = ClassUtils.getSubClassesByParentClass(BaseEntity.class, SysConstants.BASE_PACKAGE);
        for(Class clazz : classList){
            if(!clazz.isAnnotationPresent(Dict.class)){
                continue;
            }
            Dict dict = (Dict) clazz.getAnnotation(Dict.class);
            String name = dict.name();
            String value =  SysConstants.DICT_SYS_ENTITY + StringUtils.toUnderScoreCase(clazz.getSimpleName());
            SysDictType sysDictType = new SysDictType();
            sysDictType.setDictType(value);


            Class dataType = null;
            Method[] valueMethods = ClassUtils.getDeclaredMethods(clazz, DictValue.class);
            if(valueMethods.length == 0){
                Type genericSuperclass = clazz.getGenericSuperclass();
                if (genericSuperclass instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
                    Type rawType = parameterizedType.getRawType();
                    if (rawType.equals(BaseEntity.class)) {
                        Type[] typeArguments = parameterizedType.getActualTypeArguments();
                        dataType  = (Class)typeArguments[0];
                    }


                }
                if(dataType == null){
                    Field idField = ClassUtils.getField(clazz, "id");
                    if(idField != null){
                        dataType = idField.getType();
                    }
                }
            }
            else{
                Method valueMethod = valueMethods[0];
                dataType = valueMethod.getReturnType();
            }

            if(dataType == null){
                continue;
            }

            if(isNumericField(dataType)){
                sysDictType.setDataType(SysDictType.DATA_TYPE_NUMBER);
            }
            else{
                sysDictType.setDataType(SysDictType.DATA_TYPE_STRING);
            }
            sysDictType.setDictName("模块-" + name);
            sysDictType.setStatus(SysConstants.ENABLE);
            sysDictType.setRemark(name);
            sysDictTypes.add(sysDictType);
        }
        return sysDictTypes;
    }

    /**
     * 获取实体字典选项
     * @param dictType
     * @return
     */
    public static synchronized List<SysDictDataOption> getSysEntityDictOptions(String dictType){
        String serviceName = StringUtils.toCamelCase(dictType.substring(SysConstants.DICT_SYS_ENTITY.length())) + "Service";
        CurdService curdService = SpringUtils.getBean(serviceName);
        List<SysDictDataOption> dictDataOptions =  curdService.dicts();
        setDictCache(dictType, dictDataOptions, 60);
        return dictDataOptions;
    }


    /**
     * 判断是否为数值类型
     * @param fieldType
     * @return
     */
    private static boolean isNumericField(Class<?> fieldType) {
        return fieldType == byte.class ||
                fieldType == short.class ||
                fieldType == int.class ||
                fieldType == long.class ||
                fieldType == float.class ||
                fieldType == double.class ||
                fieldType == BigDecimal.class ||
                Number.class.isAssignableFrom(fieldType);
    }
}
